namespace FIREFLYX {
    export enum DefMesh {
        /** 正方体 */
        CUBE,
        /** 正四边形 */
        QUAD,
        /** 铺满整个裁剪空间的四边形 */
        QUAD_FULL_CLIP,
        /** 正球体 */
        SPHERE,
        /** 圆柱体体 */
        CYLINDER,
    }
    export enum DefTexture {
        /** 白色纹理 */
        WHITE,
        /** 灰色纹理 */
        GRAY,
        /** 黑色纹理 */
        BLACK,
        /** 黑白网格纹理 */
        GRID,
        /** 切线空间法线纹理 */
        NORMAL,
    }

    export enum DefMaterial {
        MODEL,
        SHADOW_MAP,
    }

    /** 内建默认资源 */
    export class DefaultAsset {
        private static defMesh: { [type: number]: Mesh } = {};
        private static defTexture: { [type: number]: Texture2D } = {};
        private static defGlslLib: { [libName: string]: GLSL } = {};
        private static _glslLibCodeMap: { [libName: string]: string } = {};
        private static defShader: { [key: string]: Shader } = {};
        private static _shaderGenMap: { [key: string]: () => Shader } = {};
        private static defMaterial: { [type: number]: Material } = {};

        /** glsl 库 代码字典容器 */
        public static get glslLibCodeMap() { return this._glslLibCodeMap; }
        /** 内建 着色器生成器 字典容器 */
        public static get shaderGenMap() { return this._shaderGenMap; }

        /**
         * 获取mesh
         * @param meshType mesh类型
         * @returns mesh
         */
        public static GetMesh(meshType: DefMesh): Mesh {
            let mesh = this.defMesh[meshType];
            if (!mesh) {
                mesh = new Mesh();
                mesh.name = `DefMesh_${DefMesh[meshType]}`;
                let mData: Nullable<MeshData> = null;
                //初始化资源
                switch (meshType) {
                    case DefMesh.QUAD: mData = this.GenQuadData(1); break;
                    case DefMesh.QUAD_FULL_CLIP: mData = this.GenFullClipQuadData(); break;
                    case DefMesh.CUBE: mData = this.GenCubeData(1); break;
                    case DefMesh.SPHERE: mData = this.GenSphereData(0.5, 40, 20); break;
                    case DefMesh.CYLINDER: mData = this.GenCylinderData(2, 0.5, 128); break;
                    default:
                }

                //设置data
                mData?.calcPosBoundary();
                mesh.data = mData;
                //应用参数
                mesh.apply();
                this.defMesh[meshType] = mesh;
            }
            return mesh;
        }

        /**
         * 获取纹理
         * @param textureType 纹理类型
         * @returns 纹理
         */
        public static GetTexture(textureType: DefTexture) {
            let texture = this.defTexture[textureType];
            if (!texture) {
                texture = new Texture2D(`DefTex_${DefTexture[textureType]}`);
                //初始化资源
                switch (textureType) {
                    case DefTexture.WHITE: this.SetTexOneColor(texture, new Color4(1, 1, 1, 1)); break;
                    case DefTexture.GRAY: this.SetTexOneColor(texture, new Color4(0.5, 0.5, 0.5, 1)); break;
                    case DefTexture.BLACK: this.SetTexOneColor(texture, new Color4(0, 0, 0, 1)); break;
                    case DefTexture.GRID:
                        this.SetTexGrid(texture);
                        texture.paramSetting.mipmap = true;
                        break;
                    case DefTexture.NORMAL: this.SetTexOneColor(texture, new Color4(0.5, 0.5, 1, 1)); break;
                    default:
                }

                //应用参数
                texture.apply();
                this.defTexture[textureType] = texture;
            }
            return texture;
        }

        /**
         * 获取 glsl 库
         * @param libName 库名 
         * @returns glsl
         */
        public static getGLSLLib(libName: string): Nullable<GLSL> {

            let result = this.defGlslLib[libName];
            if (!result) {
                //
                let libCode = this._glslLibCodeMap[libName];
                if (libCode) {
                    result = this.defGlslLib[libName] = new GLSL();
                    result.setCode(libCode);
                }
            }

            return result;
        }

        /**
         * 获取着色器
         * @param shaderKey 着色器key
         * @returns 着色器
         */
        public static GetShader(shaderKey: string) {
            let result = this.defShader[shaderKey];
            if (!result) {
                const shaderGen = this._shaderGenMap[shaderKey];
                if (shaderGen != null) {
                    result = this.defShader[shaderKey] = shaderGen();
                }
            }

            return result;
        }

        /**
         * 获取默认材质
         * @returns 
         */
        public static GetMaterial(materialType: DefMaterial) {
            let mtr = this.defMaterial[materialType];
            if (!mtr) {
                let shKey = "";
                switch (materialType) {
                    case DefMaterial.MODEL: shKey = BUILTIN_SHADER_BUMPED_SPECULAR; break;
                    case DefMaterial.SHADOW_MAP: shKey = BUILTIN_SHADER_BASE_SHADOW_MAP; break;
                    default:
                }
                if (shKey) {
                    mtr = this.defMaterial[materialType] = new Material(DefMaterial[materialType]);
                    mtr.shader = this.GetShader(shKey);
                    mtr.lockModify = true;
                }
            }

            return this.defMaterial[materialType];
        }

        /**
         * 生成一个 方片 模型数据
         * @param size 尺寸
         * @returns 网格数据
         */
        private static GenQuadData(size: number = 1) {
            let half = size / 2;
            let data = new MeshData();
            let poss: number[] = [];
            let uvs: number[] = [];
            let triindexs: number[] = [];
            let normals: number[] = [];
            //set data
            //   0 -------- 1
            //     |      |
            //     |      |
            //   3 -------- 2
            //
            poss.push(-half, half, 0, half, half, 0, half, -half, 0, -half, -half, 0);
            normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
            uvs.push(0, 1, 1, 1, 1, 0, 0, 0);
            triindexs.push(0, 3, 1, 1, 3, 2);
            //
            data.posArr = new Float32Array(poss);
            data.normalArr = new Float32Array(normals);
            data.uvArr = new Float32Array(uvs);
            data.triIndexArr = new Uint8Array(triindexs);
            return data;
        }

        /**
         * 生成一个 post 方片 模型数据
         * @returns 网格数据
         */
        private static GenFullClipQuadData() {
            let half = 1;
            let data = new MeshData();
            let poss: number[] = [];
            let uvs: number[] = [];
            let triindexs: number[] = [];
            // let normals: number[] = [];
            //set data
            //   0 -------- 1
            //     |      |
            //     |      |
            //   3 -------- 2
            //
            poss.push(-half, half, 0, half, half, 0, half, -half, 0, -half, -half, 0);
            // normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
            uvs.push(0, 1, 1, 1, 1, 0, 0, 0);
            triindexs.push(0, 3, 1, 1, 3, 2);
            //
            data.posArr = new Float32Array(poss);
            data.uvArr = new Float32Array(uvs);
            data.triIndexArr = new Uint8Array(triindexs);
            // data.normalArr = normals;
            return data;
        }

        /**
         * 生成一个 cube 模型数据
         * @param size 大小尺寸
         * @returns 网格数据
         */
        private static GenCubeData(size: number = 1) {
            let half = size * 0.5;
            let data = new MeshData();
            let poss: number[] = [];
            let uvs: number[] = [];
            let triindexs: number[] = [];
            let normals: number[] = [];

            //front
            this.fillIndexQuad(triindexs, poss.length);
            poss.push(
                half, half, half,
                -half, half, half,
                -half, -half, half,
                half, -half, half,
            );
            normals.push(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1);
            uvs.push(0, 1, 1, 1, 1, 0, 0, 0);
            //back
            this.fillIndexQuad(triindexs, poss.length);
            poss.push(
                -half, half, -half,
                half, half, -half,
                half, -half, -half,
                -half, -half, -half,
            );
            normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
            uvs.push(0, 1, 1, 1, 1, 0, 0, 0);
            //top
            this.fillIndexQuad(triindexs, poss.length);
            poss.push(
                -half, half, half,
                half, half, half,
                half, half, -half,
                -half, half, -half,
            );
            normals.push(0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0);
            uvs.push(0, 1, 1, 1, 1, 0, 0, 0);
            //bottom
            this.fillIndexQuad(triindexs, poss.length);
            poss.push(
                -half, -half, -half,
                half, -half, -half,
                half, -half, half,
                -half, -half, half,
            );
            normals.push(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0);
            uvs.push(0, 1, 1, 1, 1, 0, 0, 0);
            //left
            this.fillIndexQuad(triindexs, poss.length);
            poss.push(
                -half, half, half,
                -half, half, -half,
                -half, -half, -half,
                -half, -half, half,
            );
            normals.push(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0);
            uvs.push(0, 1, 1, 1, 1, 0, 0, 0);
            //right
            this.fillIndexQuad(triindexs, poss.length);
            poss.push(
                half, half, -half,
                half, half, half,
                half, -half, half,
                half, -half, -half,
            );
            normals.push(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0);
            uvs.push(0, 1, 1, 1, 1, 0, 0, 0);
            //
            data.posArr = new Float32Array(poss);
            data.normalArr = new Float32Array(normals);
            data.uvArr = new Float32Array(uvs);
            data.triIndexArr = new Uint8Array(triindexs);
            return data;
        }

        /**
         * 生成一个 圆柱体数据
         * @param height 圆柱体棱角数量
         * @param radius 圆柱高度
         * @param edgeNum 园半径
         * @returns 网格数据
         */
        private static GenCylinderData(height = 2, radius = 0.5, edgeNum = 16) {
            let halfH = height / 2;
            let stepAngle = 2 * Math.PI / edgeNum;
            let data = new MeshData();
            let poss: number[] = [];
            let uvs: number[] = [];
            let normals: number[] = [];
            let triindexs: number[] = [];

            //top center
            poss.push(0, halfH, 0);
            //bottom center
            poss.push(0, -halfH, 0);
            //uv center
            uvs.push(0.5, 0.5, 0.5, 0.5);
            //normal center
            normals.push(0, 1, 0, 0, -1, 0);  //Top & bottom normal

            let posGap = 4;
            let posGapHalf = posGap / 2;
            let centerPosNum = 2;
            for (let i = 0, len = edgeNum + 1; i < len; i++) {
                let angle = stepAngle * i;
                let _cos = Math.cos(angle);
                let _sin = Math.sin(angle);
                let x = _cos * radius;
                let z = _sin * radius;

                //pos
                poss.push(x, halfH, z);//Top pos
                poss.push(x, -halfH, z);//bottom pos
                poss.push(x, halfH, z);//top around pos
                poss.push(x, -halfH, z);//bottom around pos
                //uv
                let u = x / 2 + 0.5;
                let v = z / 2 + 0.5;
                let au = i / edgeNum;
                uvs.push(u, v);//Top uv
                uvs.push(u, v);//bottom uv
                uvs.push(au, 0);//Top around uv
                uvs.push(au, 1);//bottom around uv
                //normal
                normals.push(0, 1, 0);  //Top normal
                normals.push(0, -1, 0); //bottom normal
                normals.push(x, 0, z);  //around normal 0
                normals.push(x, 0, z);  //around normal 1

                if (i >= edgeNum) break;
                let step = i * posGap;
                let tStart = step + centerPosNum;

                //top bottom idxs
                let _p0 = tStart;
                let _p1 = tStart + posGap;
                let _p2 = tStart + 1;
                let _p3 = tStart + posGap + 1;
                triindexs.push(0, _p0, _p1);    //top tri
                triindexs.push(1, _p3, _p2);    //bottom tri
                //around idxs
                let _pa0 = _p0 + posGapHalf;
                let _pa1 = _p1 + posGapHalf;
                let _pa2 = _p2 + posGapHalf;
                let _pa3 = _p3 + posGapHalf;
                triindexs.push(_pa0, _pa2, _pa1);  //around tri 0
                triindexs.push(_pa1, _pa2, _pa3);  //around tri 1
            }

            data.posArr = new Float32Array(poss);
            data.uvArr = new Float32Array(uvs);
            data.normalArr = new Float32Array(normals);
            data.triIndexArr = new Uint16Array(triindexs);
            return data;
        }

        /**
         * 生成一个球形mesh
         * @param size 尺寸（半径）
         * @param xSegments X 轴向点分段数
         * @param ySegments Y 轴向点分段数
         * @returns 网格数据
         */
        private static GenSphereData(size: number = 0.5, xSegments: number = 24, ySegments: number = 12) {
            let data = new MeshData();
            let poss: number[] = [];
            let uvs: number[] = [];
            let triindexs: number[] = [];
            let normals: number[] = [];

            xSegments = Math.max(3, Math.floor(xSegments));
            ySegments = Math.max(2, Math.floor(ySegments));

            let ix, iy;

            let index = 0;
            let grid = [];

            // generate vertices, normals and uvs
            for (iy = 0; iy <= ySegments; iy++) {
                let verticesRow = [];
                let v = iy / ySegments;
                for (ix = 0; ix <= xSegments; ix++) {
                    let u = ix / xSegments;
                    // vertex
                    let x = - size * Math.cos(u * Math.PI * 2) * Math.sin(v * Math.PI);
                    let y = size * Math.cos(v * Math.PI);
                    let z = size * Math.sin(u * Math.PI * 2) * Math.sin(v * Math.PI);
                    poss.push(x, y, z);

                    let val = poss.length % 3;
                    if (val == 0) {
                        // normal
                        let nx = x;
                        let ny = y;
                        let nz = z;
                        let num: number = Math.sqrt(nx * nx + ny * ny + nz * nz);
                        if (num > Number.MIN_VALUE) {
                            nx = nx / num;
                            ny = ny / num;
                            nz = nz / num;
                        } else {
                            nx = 0;
                            ny = 0;
                            nz = 0;
                        }
                        normals.push(nx, ny, nz);
                    }

                    // uv
                    uvs.push(1 - u, v);
                    verticesRow.push(index++);
                }
                grid.push(verticesRow);
            }

            // indices
            for (iy = 0; iy < ySegments; iy++) {
                for (ix = 0; ix < xSegments; ix++) {
                    let a = grid[iy][ix + 1];
                    let b = grid[iy][ix];
                    let c = grid[iy + 1][ix];
                    let d = grid[iy + 1][ix + 1];

                    if (iy !== 0) triindexs.push(a, d, b);
                    if (iy !== ySegments - 1) triindexs.push(b, d, c);
                }
            }
            //
            data.posArr = new Float32Array(poss);
            data.uvArr = new Float32Array(uvs);
            data.normalArr = new Float32Array(normals);
            data.triIndexArr = new Uint16Array(triindexs);

            return data;
        }

        /** 往quad数据填入三角索引 */
        private static fillIndexQuad(indexs: number[], startIdx: number) {
            startIdx /= 3;
            indexs.push(startIdx + 0, startIdx + 3, startIdx + 1, startIdx + 1, startIdx + 3, startIdx + 2);
        }

        /**
         * 设置单色纹理 
         * @param tex 纹理 
         * @param color 颜色
         */
        private static SetTexOneColor(tex: Texture2D, color: Color4) {
            let size = 2;
            let _w = size * 4;
            let pixelData: Uint8Array = new Uint8Array(size * _w);
            let r = Math.floor(color.r * 255);
            let g = Math.floor(color.g * 255);
            let b = Math.floor(color.b * 255);
            let a = Math.floor(color.a * 255);
            for (let i = 0, len = size * _w; i < len; i += 4) {
                pixelData[i] = r;
                pixelData[i + 1] = g;
                pixelData[i + 2] = b;
                pixelData[i + 3] = a;
            }

            //set img
            tex.setTextureData(pixelData, size, size);
        }

        /**
         * 设置黑白格子纹理 
         * @param tex 纹理 
         */
        private static SetTexGrid(tex: Texture2D) {
            let size = 128;
            let w = size * 4;
            let pixelData: Uint8Array = new Uint8Array(size * w);
            let halfW = w / 2;
            let halfH = size / 2;
            let one = 255;
            let zero = 0;
            for (let y = 0; y < size; y++) {
                let numY = y * w;
                let _yIsSmall = (y < halfH);
                for (let x = 0; x < w; x += 4) {
                    let c = _yIsSmall == (x < halfW) ? one : zero;
                    pixelData[numY + x] = c;
                    pixelData[numY + x + 1] = c;
                    pixelData[numY + x + 2] = c;
                    pixelData[numY + x + 3] = one;
                }
            }

            //set img
            tex.setTextureData(pixelData, size, size);
        }

    }
}